﻿using System;
using System.Configuration;
using System.Web;
using System.Web.Caching;
using ITSP.SharePoint.CacheRefresh.Common;
using ITSP.SharePoint.CacheRefresh.Interfaces;
using ITSP.SharePoint.CacheRefresh.SpecialCases;
using Microsoft.Practices.SharePoint.Common.Logging;
using Microsoft.Practices.SharePoint.Common.ServiceLocation;
using Microsoft.SharePoint.Administration;

namespace ITSP.SharePoint.CacheRefresh
{
    /// <summary>
    /// Base Configuration Manager used to store configurations with.
    /// Use this to base your configuration manager from.
    /// <createdby>Simon Doy</createdby>
    /// </summary>
    public abstract class BaseConfigurationManager : IConfigurationManager
    {
        #region Protected Members

        /// <summary>
        /// The SharePoint Logging Component
        /// </summary>
        protected ILogger logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();

        /// <summary>
        /// The Cache Key Internal Value
        /// </summary>
        protected string _cacheKey = "";

        /// <summary>
        /// The Cache Configuration Item List Key
        /// </summary>
        protected string _listCacheKey = "";

        /// <summary>
        /// The Configuration Storage Key for use against the storage mechanism.
        /// </summary>
        protected string _configurationStorageKey = "";

        /// <summary>
        /// The Cache System
        /// </summary>
        protected Cache _cache = null;

        /// <summary>
        /// The Site where the Cache Configuraiton List is located.
        /// </summary>
        protected string _cacheListHostUrl = "";

        #endregion

        #region Properties

        /// <summary>
        /// List Cache Key used with the Cache Configuration
        /// </summary>
        public virtual string ListCacheKey { get { return _listCacheKey; } protected set { _listCacheKey = value; } }

        /// <summary>
        /// Cache Key used with the Cache Http Runtime.
        /// </summary>
        public virtual string CacheKey { get { return _cacheKey; } protected set { _cacheKey = value; } }

        /// <summary>
        /// The Storage Key used to Load/Save the Configuration Item within the objects storage.
        /// </summary>
        public virtual string ConfigurationStorageKey { get { return _configurationStorageKey; } protected set { _configurationStorageKey = value; } }

        /// <summary>
        /// The Url that is hosting the Cache List.
        /// </summary>
        public virtual string CacheListHostUrl
        {
            get { return _cacheListHostUrl; } 
            protected set { _cacheListHostUrl = value; }
        }

        /// <summary>
        /// Register to receive events when the Cache Items are Removed.
        /// </summary>
        public event CacheItemRemovedCallback CacheItemRemoved;

        /// <summary>
        /// Register to receive events when the Cache Item are to be Updated.
        /// This is not currently implemented.
        /// </summary>
        private event CacheItemUpdateCallback CacheItemUpdated;

        #endregion

        #region Constructors

        /// <summary>
        /// Default Constructor
        /// </summary>
        protected BaseConfigurationManager()
        {
            _cache = HttpRuntime.Cache;
            CacheItemRemoved += OnItemCacheRemovedCallback;
            //CacheItemUpdated += OnItemCacheUpdatedCallback;
        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <param name="cacheListHostUrl">The Url of the Web Application / Site that is hosting the Cache List. This is used by the Configuration Manager.</param>
        protected BaseConfigurationManager(string cacheListHostUrl)
        {
            _cache = HttpRuntime.Cache;
            CacheListHostUrl = cacheListHostUrl;

            CacheItemRemoved += OnItemCacheRemovedCallback;
            //CacheItemUpdated += OnItemCacheUpdatedCallback;
        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <param name="cacheListHostUrl">The Url of the Web Application / Site that is hosting the Cache List. This is used by the Configuration Manager.</param>
        /// <param name="listCacheKey">This is the Cache Configuration List Item Id</param>
        /// <param name="cacheKey">This is the Key to use to store the Item in the IIS ASP.Net Cache</param>
        /// <param name="configurationStorageKey">This is used to store the Configuration in the Data Access Layer Storage.</param>
        protected BaseConfigurationManager(string cacheListHostUrl, string listCacheKey, string cacheKey, string configurationStorageKey)
        {
            _cache = HttpRuntime.Cache;
            CacheListHostUrl = cacheListHostUrl;
            ListCacheKey = listCacheKey;
            CacheKey = cacheKey;
            ConfigurationStorageKey = configurationStorageKey;

            CacheItemRemoved += OnItemCacheRemovedCallback;
            //CacheItemUpdated += OnItemCacheUpdatedCallback;
        }

        #endregion

        /// <summary>
        /// Saves the Configuration object to the SPFarm Property Bag.
        /// This operation will also refresh the cache using the Cache Dependencies.
        /// </summary>
        /// <param name="configuration">This is the configuration object to save using Generics.</param>
        /// <returns>true if successful otherwise false.</returns>
        public abstract bool Save<T>(T configuration);

        /// <summary>
        /// Loads in the configuration from SharePoint Farm Property Bag.
        /// </summary>
        /// <returns>The configuration object of Generic Type.</returns>
        public abstract T Load<T>();
        
        /// <summary>
        /// Refreshes the Configuration by Invalidate the Cache using the provided ListCacheKey.
        /// </summary>
        /// <returns>True/False depending if the operation was successful or not.</returns>
        public virtual bool Refresh()
        {
            bool bSuccess = false;
            try
            {
                ICacheRefreshManager cacheManager = new CacheRefreshManager(_cacheListHostUrl);
                bSuccess = cacheManager.InvalidateCache(ListCacheKey);
            }
            catch (Exception ex)
            {
                logger.TraceToDeveloper(ex, 10000, TraceSeverity.High, Constants.Logging.DefaultLoggingCategory);
                throw;
            }
            return bSuccess;

        }

        /// <summary>
        /// This function will be called when an item is removed from the Cache.
        /// </summary>
        /// <param name="key">the cache key that was affected</param>
        /// <param name="value">the object that was removed</param>
        /// <param name="reason">the reason that the cache item was removed.</param>
        public static void OnItemCacheRemovedCallback(string key, object value, CacheItemRemovedReason reason)
        {
            ILogger logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>(); 
            logger.TraceToDeveloper(String.Format("Cache Item Key: {0} Object: {1} Reason: {2}", key, value, reason), 0, TraceSeverity.Monitorable, Constants.Logging.DefaultLoggingCategory);
        }

        /// <summary>
        /// This function will be called when an item is to be updated in the Cache.
        /// </summary>
        /// <param name="key">the cache key that was affected</param>
        /// <param name="reason">the reason</param>
        /// <param name="cacheObject">the object that was updated</param>
        /// <param name="cacheDependency">the cache dependency that caused it</param>
        /// <param name="cacheDateTime">the updated date/time to end caching at.</param>
        /// <param name="cacheTimeSpan">the updated time period to end caching at.</param>
        private static void OnItemCacheUpdatedCallback(string key, CacheItemUpdateReason reason, out object cacheObject, out CacheDependency cacheDependency, out DateTime cacheDateTime, out TimeSpan cacheTimeSpan)
        {
            ILogger logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();

            cacheObject = new object();
            cacheDependency = null;
            cacheDateTime = DateTime.Now;
            cacheTimeSpan = TimeSpan.FromMinutes(10);

            logger.TraceToDeveloper(String.Format("Updating Cache Item Key: {0} Object: {1} Reason: {2}", key, cacheObject, reason), 0, TraceSeverity.Monitorable, Constants.Logging.DefaultLoggingCategory);

        }

        /// <summary>
        /// Get the Configuration from the cache.
        /// </summary>
        /// <returns>The configuration object of Generic Type.</returns>
        protected virtual T GetFromCache<T>()
        {
            T configuration = default(T);

            if (_cache != null && _cache.Get(CacheKey) != null)
            {
                configuration = (T)_cache.Get(CacheKey);
            }

            return configuration;
        }

        /// <summary>
        /// Get the Configuration from the cache.
        /// </summary>
        /// <param name="cacheKey">String Key to get the item from cache using</param>
        /// <returns>The configuration object of Generic Type.</returns>
        protected virtual T GetFromCache<T>(string cacheKey)
        {
            T configuration = default(T);

            if (_cache != null && _cache.Get(cacheKey) != null)
            {
                configuration = (T)_cache.Get(cacheKey);
            }

            return configuration;
        }

        /// <summary>
        /// Add the Configuration to the Cache.
        /// </summary>
        /// <param name="configuration">The configuration object which is of a Generic Type</param>
        /// <returns>true/false</returns>
        protected virtual bool AddToCache<T>(T configuration)
        {

            bool bSuccess = false;

            try
            {
                if (_cache != null)
                {
                    SPCacheFileDependency dependency = GetCacheDependencyObject();
                    if (dependency.Exists && dependency.Enabled)
                    {
                        TimeSpan cacheTime = TimeSpan.FromMinutes(dependency.CacheLifeTime);
                        DateTime cacheExpiration = (cacheTime.Minutes > 0) ? DateTime.Now.Add(cacheTime) : Cache.NoAbsoluteExpiration;
                        logger.TraceToDeveloper(String.Format("Adding {3} to Cache {0} for {1} Minutes, Expires at {2}", CacheKey, cacheTime.TotalMinutes, cacheExpiration.ToLongTimeString(), configuration.GetType()), 0, TraceSeverity.Monitorable, Constants.Logging.DefaultLoggingCategory);

                        _cache.Insert(CacheKey, configuration, dependency, cacheExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, CacheItemRemoved);
                        bSuccess = true;
                    }
                }
            }
            catch (Exception)
            {

                throw;
            }

            return bSuccess;
        }

        /// <summary>
        /// Add the Configuration to the Cache.
        /// </summary>
        /// <param name="configuration">The configuration object which is of a Generic Type</param>
        /// <param name="cacheKey">String Key to add the item to using</param>
        /// <returns>true/false</returns>
        protected virtual bool AddToCache<T>(T configuration, string cacheKey)
        {

            bool bSuccess = false;

            try
            {
                if (_cache != null)
                {
                    SPCacheFileDependency dependency = GetCacheDependencyObject();
                    if (dependency.Exists && dependency.Enabled)
                    {
                        TimeSpan cacheTime = TimeSpan.FromMinutes(dependency.CacheLifeTime);
                        DateTime cacheExpiration = (cacheTime.Minutes > 0) ? DateTime.Now.Add(cacheTime) : Cache.NoAbsoluteExpiration;
                        logger.TraceToDeveloper(String.Format("Adding {3} to Cache {0} for {1} Minutes, Expires at {2}", cacheKey, cacheTime.TotalMinutes, cacheExpiration.ToLongTimeString(), configuration.GetType()), 0, TraceSeverity.Monitorable, Constants.Logging.DefaultLoggingCategory);

                        _cache.Insert(cacheKey, configuration, dependency, cacheExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, CacheItemRemoved);
                        bSuccess = true;
                    }
                }
            }
            catch (Exception)
            {

                throw;
            }

            return bSuccess;
        }

        /// <summary>
        /// Get the Cache Dependency Object for the Configuration object.
        /// </summary>
        /// <returns>A valid <see cref="SPCacheFileDependency"/> if the configuration list cache key has been found.
        /// Otherwise a <see cref="InvalidSPCacheFileDependency"/> is returned
        /// </returns>
        protected SPCacheFileDependency GetCacheDependencyObject()
        {
            SPCacheFileDependency dependency = new InvalidSPCacheFileDependency();
            try
            {
                
                if (!String.IsNullOrEmpty(_cacheListHostUrl))
                {
                    CacheRefreshManager refreshManager = new CacheRefreshManager(CacheListHostUrl);
                    dependency = refreshManager.GetCacheDependency(ListCacheKey);
                }
                else
                {
                    logger.TraceToDeveloper("Warning Cache Configuration List Not Found",0, TraceSeverity.Medium, Constants.Logging.DefaultLoggingCategory);
                }
            }
            catch (Exception ex)
            {
                //ignore
            }


            return dependency;
        }
    }

}
